iT邦幫忙

第 11 屆 iThome 鐵人賽

11

首先先來定義一下什麼是資料庫正規化:
關聯式資料庫正規化的過程,其實就是將一些實體的描述資料,透過一定的程序,將表單簡化,直到一張表單只單純描述一個事實為止。
簡單來說,也就是透過一定的程序,去除資料庫中冗餘的內容,讓資料能夠井然有序且有效率的儲存。

為何要執行正規化?

  1. 提昇儲存資料與資料庫操作效率
  2. 減少資料異常
  3. 使資料庫維護更容易

正規化的資料庫特性

經過正規化後的資料庫,應具備以下特性:

  1. 欄位唯一性:每個欄位只儲存一項資料
  2. 主關鍵欄位:每筆資料都擁有一個主鍵,來區別這些資料
  3. 功能關聯性:欄位之間的關聯應該要明確
  4. 欄位獨立性:欄位之間不應存在遞移相依

如果完全不做正規化會怎樣?

我們先來看一下這張完全未經過任何正規化的table:

orders

custmor_id custmor_name gender items prices store_name address date sex
1 阿寶 1 鉛筆, 橡皮擦 20, 50 1 久成久 東區大學路
2 豆芽 3 牛奶, 三明治 70, 10 3 全家 東區北門路
3 老皮 1 鉛筆, 牛奶 20, 70 1 久成久 東區大學路

很明顯的,有幾個大問題存在於這張表中:

  1. 他在items和prices這兩個欄位裡面都出現了一個欄位儲存兩項以上的資料的問題。
  2. gender和sex這兩個欄位,他們在意義上是重複的。其實可以保留其中一個好了,不必浪費空間跟時間來儲存冗余的資料。
  3. 這些資料並沒有一個unique的primary key可以辨識他們的區別,換句話說,這張表容許裡面存放兩筆以上內容一模一樣的資料。

以上這些問題都有可能提高重複資料及資料不一致問題發生的機率。

問題:

  1. 一個欄位儲存多筆資料,違反資料的原子性(atomicity)。
  2. 出現意義上重複的欄位。
  3. 缺乏主鍵(Primary Key)。

第一正規化

因此我們要透過第一正規化來修正以上的問題。

第一NF要完成的工作:

  1. 一個欄位只能有單一值
  2. 消除意義上重複的欄位
  3. 決定主鍵(必須是unique)

流程:
1.把原本一個欄位儲存多項資料的部份分開來儲存。
2.刪除意義重複的column。
3.設定一個primary key,作為一筆資料的唯一識別。

經過第一正規化後的表就會長成這樣,我們把每一項商品的消費都分開來儲存,然後把多餘的欄位刪掉。
現在這張表中的每個欄位都會是有意義的。
然後定義primary key為custmor_id和item_id組成複合主鍵。

orders

custmor_id custmor_name gender item_id item_name price count total_amount store_name address date
1 阿寶 1 鉛筆 20 1 1 20 久成久 東區大學路
1 阿寶 2 橡皮擦 50 2 2 100 久成久 東區大學路
2 豆芽 3 牛奶 70 3 2 210 全家 東區北門路
2 豆芽 4 三明治 10 4 1 40 全家 東區北門路
3 老皮 1 鉛筆 20 1 2 20 久成久 東區大學路
3 老皮 3 牛奶 70 2 1 140 全家 東區北門路

然而,看看這張經過第一正規化後的表,應該還是會覺得好像哪裡怪怪的,感覺很雜亂。
例如他的每一筆消費紀錄都要反覆紀錄消費者的性別、商品名稱、商店的名稱和地址,重複內容過多。

假設今天老皮在全家訂購了一百個三明治,那我就要重複輸入:姓名=>老皮,性別=>男性,商店的地址=>東區北門路。

又如果這間全家搬到了民族路上,那可能還需要在這一百筆資料裡一筆一筆做修改,非常沒有效率。

問題:

  • 出現過多重複資料

因此我們要透過第二正規化來修正上述的問題

第二正規化

第二NF要完成的工作:

  1. 消除部分相依

部分相依的意思為跟主鍵只有一部份有關係,另一部份沒有關係的欄位,我們要把這些欄位獨立於另一張表。
在上述例子中,主鍵為customer_id和item_id,那像customer_name就只和customer_id相依、item_name只和item_id相依,這些都是應該被移出去到另一張表存放的欄位。

部分相依指通常發生在使用複合主鍵的情況下。如果一張表的主鍵由多個欄位組成,某些非主鍵欄位可能只依賴於主鍵中的一部分,這樣就會造成部分相依。
許多框架在建表時預設會以id作為pk,若該表已完成第一正規化,設定id為pk則直接滿足第二正規化,因為pk為單一鍵(已經是最小單位了,自然不會有和pk『部分相依』的情況)

流程:

  1. 新增一個獨立的table來儲存這些一直重複出現的欄位
  2. 透過foreign key來關聯這些table

第二正規化後,另外建了兩個獨立的表,items跟consumers來儲存消費者資訊和商品資訊。
orders分別和items跟customers關聯,把這些資料分出去儲存後,orders就單純的只儲存消費者和購買的商品,以及消費日期三種資料而已。
在orders中,消費者對應到customers的姓名,商品對應到items的名稱,而消費者和商品就是orders的foreign key。

customers

id name gender
1 阿寶
2 豆芽
3 老皮

stores

id name address
1 久成久 東區大學路
2 全家 東區北門路

items

id name price store_id
1 鉛筆 20 1
2 橡皮擦 50 1
3 牛奶 70 2
4 三明治 10 2

orders

custmor_id item_id count total_amount date
1 1 1 20 12/17
1 2 2 100 12/17
2 3 3 210 12/18
2 4 1 40 12/18
3 1 4 20 12/19
3 3 1 140 12/19

然而,這樣還是會有一個問題,就是遞移關係。
所謂遞移關係,在這個範例裡就是指:total_amount是依賴price及count的資訊,而item_id和count又和主鍵直接相關,那total_amount和主鍵之間的關係就是遞移關係。

  • 消除遞移相依:非主鍵屬性的欄位都只能和候選鍵相關,非主鍵屬性的欄位彼此間應該要是獨立無關的

遞移相依:欄位1和主鍵相關,欄位2和欄位1相關,欄位2和主鍵就是遞移相依

候選鍵:欄位組合讓資料能是唯一的,並且是最小唯一

用個比較容易理解的方式來說明:為了避免count改變而total_amount沒改到造成資料錯誤,應該把total_amount這個從這張表中移除。

問題:存在主鍵以外的欄位與主鍵間接(遞移)相依

第三正規化

在第三正規化的規範中,要消除資料表中與主欄位的遞移相依

第三NF要完成的工作:

  • 消除資料表中的遞移相依

customers

id name gender
1 阿寶
2 豆芽
3 老皮

stores

id name address
1 久成久 東區大學路
2 全家 東區北門路

items

id name price store_id
1 鉛筆 20 1
2 橡皮擦 50 1
3 牛奶 70 2
4 三明治 10 2

orders

customer_id item_id count date
1 1 1 12/17
1 2 2 12/17
2 3 3 12/18
2 4 1 12/18
3 1 4 12/19
3 3 1 12/19

BUT,

但是,並不是每次建表都要照著正規化的規範才是最好的。

假如業務需求就單純是要很高頻率的去查詢某個消費者購買的商品名稱及商店地址的資料,就必須先從customers關聯到items,再關聯到stores。
這樣第二正規化之後所帶來的好處其實並不比他所帶來的開銷還要大,其實只要做到第一正規化就好了。

又或著,無論這個商品價格在消費者訂購之後進行幾次更改,需求就是要能查得到他購買當下的價格,那麼上述範例在遵循第二、第三正規化進行修正後,反而更不適用於這個情境。

結論

因此,資料庫正規化並不是什麼完全不可違背的教條,要進行到什麼程度,應視對資料庫操作的需求和資料量而定,而不是每次建表都只要無腦的全部照著正規化的規範去設計你的資料表就好。


上一篇
Day 31 用Laravel儲存圖片
下一篇
Day 33 Laravel 實做雙人橋牌之心得分享
系列文
後端基礎PHP+Mysql & Laravel 30日養成計畫36
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
微中子
iT邦新手 4 級 ‧ 2020-02-18 18:04:42

正規化有五種,這篇介紹了前三種
有興趣可以看我寫的,五種都有講到 (雖然就是英文的)

Good RDB Design with the Concept of Normal Forms

阿寶 iT邦新手 5 級 ‧ 2022-01-24 10:59:58 檢舉

感謝分享

我要留言

立即登入留言